/**
 * Hook-Fetch Wrapper Tests
 * 
 * Unit tests for the core HookFetchWrapper class functionality
 * including request identification, deduplication, and tracking.
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { HookFetchWrapper } from '../hookFetchWrapper';
import type { RequestConfig, HookFetchWrapperConfig } from '../../types/hookFetchTypes';
import { DUPLICATE_REQUEST_ERROR_CODE } from '../../types/hookFetchTypes';

describe('HookFetchWrapper', () => {
  let wrapper: HookFetchWrapper;
  let config: HookFetchWrapperConfig;

  beforeEach(() => {
    config = {
      baseURL: 'https://api.example.com',
      timeout: 5000,
      headers: {
        'Content-Type': 'application/json'
      }
    };
    wrapper = new HookFetchWrapper(config);
  });

  describe('Constructor', () => {
    it('should initialize with provided configuration', () => {
      const wrapperConfig = wrapper.getConfig();
      expect(wrapperConfig.baseURL).toBe(config.baseURL);
      expect(wrapperConfig.timeout).toBe(config.timeout);
      expect(wrapperConfig.headers).toEqual(config.headers);
    });

    it('should use default timeout when not provided', () => {
      const configWithoutTimeout = { baseURL: 'https://api.example.com' };
      const wrapperWithDefaults = new HookFetchWrapper(configWithoutTimeout);
      const wrapperConfig = wrapperWithDefaults.getConfig();
      expect(wrapperConfig.timeout).toBe(10000); // DEFAULT_REQUEST_TIMEOUT
    });
  });

  describe('Request Identification', () => {
    it('should generate consistent identifiers for identical requests', () => {
      const requestConfig1: RequestConfig = {
        url: '/api/users',
        method: 'GET',
        params: { page: 1, limit: 10 }
      };

      const requestConfig2: RequestConfig = {
        url: '/api/users',
        method: 'GET',
        params: { page: 1, limit: 10 }
      };

      // Access private method through type assertion for testing
      const wrapper1 = wrapper as any;
      const id1 = wrapper1.getRequestIdentify(requestConfig1);
      const id2 = wrapper1.getRequestIdentify(requestConfig2);

      expect(id1).toBe(id2);
    });

    it('should generate different identifiers for different requests', () => {
      const requestConfig1: RequestConfig = {
        url: '/api/users',
        method: 'GET',
        params: { page: 1 }
      };

      const requestConfig2: RequestConfig = {
        url: '/api/users',
        method: 'GET',
        params: { page: 2 }
      };

      const wrapper1 = wrapper as any;
      const id1 = wrapper1.getRequestIdentify(requestConfig1);
      const id2 = wrapper1.getRequestIdentify(requestConfig2);

      expect(id1).not.toBe(id2);
    });

    it('should handle requests without params or data', () => {
      const requestConfig: RequestConfig = {
        url: '/api/status',
        method: 'GET'
      };

      const wrapper1 = wrapper as any;
      const id = wrapper1.getRequestIdentify(requestConfig);

      expect(id).toBe('GET:/api/status::');
    });

    it('should sort object keys for consistent identification', () => {
      const requestConfig1: RequestConfig = {
        url: '/api/users',
        method: 'POST',
        data: { name: 'John', age: 30 }
      };

      const requestConfig2: RequestConfig = {
        url: '/api/users',
        method: 'POST',
        data: { age: 30, name: 'John' }
      };

      const wrapper1 = wrapper as any;
      const id1 = wrapper1.getRequestIdentify(requestConfig1);
      const id2 = wrapper1.getRequestIdentify(requestConfig2);

      expect(id1).toBe(id2);
    });
  });

  describe('Request Deduplication', () => {
    it('should allow first request and track it as pending', () => {
      const requestConfig: RequestConfig = {
        url: '/api/users',
        method: 'GET'
      };

      expect(wrapper.getPendingRequestsCount()).toBe(0);

      // Since we can't actually make requests without plugins, we'll test the tracking logic
      const wrapper1 = wrapper as any;
      const requestKey = wrapper1.getRequestIdentify(requestConfig);
      const isDuplicate = wrapper1.handleRequestDeduplication(requestKey);

      expect(isDuplicate).toBe(false);
    });

    it('should track pending requests correctly', () => {
      expect(wrapper.getPendingRequestsCount()).toBe(0);
      expect(wrapper.getPendingRequestKeys()).toEqual([]);
    });

    it('should clear pending requests', () => {
      wrapper.clearPendingRequests();
      expect(wrapper.getPendingRequestsCount()).toBe(0);
    });
  });

  describe('Request Method', () => {
    it('should reject duplicate requests with correct error code', async () => {
      const requestConfig: RequestConfig = {
        url: '/api/users',
        method: 'GET'
      };

      // Mock the executeRequest method to avoid actual HTTP calls
      const wrapper1 = wrapper as any;
      wrapper1.executeRequest = vi.fn().mockResolvedValue({
        code: 200,
        data: { users: [] },
        msg: 'Success'
      });

      // First request should be allowed
      const firstRequest = wrapper.request(requestConfig);

      // Second identical request should be rejected
      try {
        await wrapper.request(requestConfig);
        expect.fail('Second request should have been rejected');
      } catch (error) {
        expect(error.message).toContain(DUPLICATE_REQUEST_ERROR_CODE);
      }

      // Wait for first request to complete
      await firstRequest;
    });
  });

  describe('Utility Methods', () => {
    it('should allow setting custom request identifier generator', () => {
      const customGenerator = (config: RequestConfig) => `custom:${config.url}`;
      wrapper.setRequestIdentifierGenerator(customGenerator);

      const requestConfig: RequestConfig = {
        url: '/api/test',
        method: 'GET'
      };

      const wrapper1 = wrapper as any;
      const id = wrapper1.getRequestIdentify(requestConfig);
      expect(id).toBe('custom:/api/test');
    });

    it('should return configuration copy', () => {
      const wrapperConfig = wrapper.getConfig();
      wrapperConfig.baseURL = 'modified';
      
      const originalConfig = wrapper.getConfig();
      expect(originalConfig.baseURL).toBe(config.baseURL);
    });
  });

  describe('Object Sorting', () => {
    it('should sort object keys consistently', () => {
      const wrapper1 = wrapper as any;
      const unsorted = { c: 3, a: 1, b: 2 };
      const sorted = wrapper1.sortObject(unsorted);
      
      expect(Object.keys(sorted)).toEqual(['a', 'b', 'c']);
      expect(sorted).toEqual({ a: 1, b: 2, c: 3 });
    });

    it('should handle null and undefined objects', () => {
      const wrapper1 = wrapper as any;
      expect(wrapper1.sortObject(null)).toBe(null);
      expect(wrapper1.sortObject(undefined)).toBe(undefined);
    });

    it('should handle non-object values', () => {
      const wrapper1 = wrapper as any;
      expect(wrapper1.sortObject('string')).toBe('string');
      expect(wrapper1.sortObject(123)).toBe(123);
    });
  });
});